import {ReactNode, useCallback, useContext, useEffect, useMemo, useState} from 'react';
import {Container, Graphics, Sprite, Text} from '@pixi/react';
import {TextStyle} from 'pixi.js'
import {DropShadowFilter} from 'pixi-filters';
import {CameraViewContext} from "../CameraView/CameraViewContext.ts";

import './PianoKey.css';
import {PianoKeyboardElement, PianoKeyContext, PianoKeyElement} from "./PianoKeyContext.ts";
import {Graphics as PixiGraphics} from "@pixi/graphics";
import {PianoNote} from "../../services/Piano.ts";


interface PianoKeyProps {

    // 琴键区域高度
    height: number;

    whiteKeyWidth: number;

    blackKeyWidth: number;

    attackedNotes: PianoNote[];

    // 琴键上方的区域，可用于渲染琴谱，瀑布流等
    topArea?: ReactNode;

    onTriggerAttack?: (note: string) => void;

    onTriggerRelease?: (note: string) => void;
}

/**
 * 只展示在 start 和 end 之间的琴键
 * @param elements 该数组已经按offset排过序了
 * @param startOffsetX 屏幕左侧相对于原点 （A0 左侧）的水平距离
 * @param endOffsetX 屏幕又侧相对于原点 （A0 左侧）的水平距离
 * @param attackedNotes
 */
function displayPianoKeys(elements: PianoKeyElement[], startOffsetX: number, endOffsetX: number, attackedNotes: string[]) {

    const result: PianoKeyElement[] = [];

    const attackedNoteSet: Set<string> = new Set(attackedNotes);

    for (const key of elements) {

        const offsetX = key.offsetX;

        if ((offsetX + key.width) < startOffsetX) {
            continue;
        } else if ((offsetX + key.width) >= startOffsetX && offsetX < endOffsetX) {
            // 计算该琴键在屏幕中的坐标
            key.x = offsetX - startOffsetX;

            key.attacked = attackedNoteSet.has(key.note);

            result.push(key);
        } else if (offsetX > endOffsetX) {
            break;
        }
    }
    return result;
}

const shadowFilter = new DropShadowFilter();

// 琴键上面的线条宽度，真正的琴键高度是height-PIANO_KEY_TOP_LINE_HEIGHT
const PIANO_KEY_TOP_LINE_HEIGHT = 3;

export function AppPianoKey(props: PianoKeyProps) {

    const [keyElements, setKeyElements] = useState<PianoKeyElement[]>([]);

    const [attackedNotes, setAttackedNotes] = useState<string[]>([]);

    const {
        sceneWidth,
        cameraViewOffsetX,
        cameraViewWidth,
        cameraViewHeight
    } = useContext(CameraViewContext);

    const pianoKeyboardElement = useMemo(() => {
        return new PianoKeyboardElement(props.whiteKeyWidth, props.blackKeyWidth);
    }, [props.whiteKeyWidth, props.blackKeyWidth]);

    // 计算顶部区域的高度
    const topAreaHeight = useMemo(() => {
        return cameraViewHeight - props.height;
    }, [cameraViewHeight, props.height]);

    const keyTop = useMemo(() => {
        return topAreaHeight + PIANO_KEY_TOP_LINE_HEIGHT;
    }, [topAreaHeight]);

    const keyboardHeight = useMemo(() => {
        return props.height - PIANO_KEY_TOP_LINE_HEIGHT;
    }, [props.height]);

    useEffect(() => {
        setKeyElements(pianoKeyboardElement.getSortedPianoKeys());
    }, [pianoKeyboardElement]);

    const onTriggerAttack = (key: PianoKeyElement) => {

        setAttackedNotes(value => [...value, key.note]);

        if (props.onTriggerAttack) {
            props.onTriggerAttack(key.note);
        }
    }

    const onTriggerRelease = (key: PianoKeyElement) => {

        setAttackedNotes(value => value.filter(each => each !== key.note))

        if (props.onTriggerRelease) {
            props.onTriggerRelease(key.note);
        }
    }

    const renderedKeys = displayPianoKeys(
        keyElements,
        cameraViewOffsetX,
        cameraViewOffsetX + cameraViewWidth,
        props.attackedNotes.map(each => each.name).concat(attackedNotes),
    );

    const draw = useCallback(
        (g: PixiGraphics) => {
            g.clear();

            g.beginFill("#5A3755");
            g.drawRect(0, topAreaHeight, sceneWidth, PIANO_KEY_TOP_LINE_HEIGHT);
            g.endFill();
        },
        [props.height, sceneWidth],
    );

    return <>
        <PianoKeyContext.Provider value={{
            pianoKeyboard: pianoKeyboardElement,
            pianoKeyboardWidth: sceneWidth,
            pianoKeyboardHeight: props.height,
            topAreaHeight: topAreaHeight
        }}>
            {props.topArea}
        </PianoKeyContext.Provider>
        <Graphics draw={draw}/>
        {
            renderedKeys.filter(key => key.type === 'white')
                .map((key: PianoKeyElement) => {
                    return <Container
                        key={key.note}
                        position={[key.x || 0, keyTop]}>
                        <Sprite
                            x={0}
                            y={0}
                            width={key.width}
                            height={keyboardHeight}
                            image={key.attacked ? `/whitekey_attack.png` : `/whitekey.png`}
                            eventMode={'static'}
                            mousedown={() => onTriggerAttack(key)}
                            mouseup={() => onTriggerRelease(key)}
                            touchstart={() => onTriggerAttack(key)}
                            touchend={() => onTriggerRelease(key)}
                        >
                        </Sprite>
                        <Text
                            text={key.note}
                            anchor={0.5}
                            x={key.width / 2}
                            y={keyboardHeight - 20}
                            style={
                                new TextStyle({
                                    align: 'center',
                                    fontFamily: '"Source Sans Pro", Helvetica, sans-serif',
                                    fontSize: 16,
                                    fontWeight: '400',
                                })
                            }
                        >
                        </Text>
                    </Container>
                })
        }
        {
            renderedKeys.filter(note => note.type === 'black')
                .map((key: PianoKeyElement) => {
                    return <Sprite
                        key={key.note}
                        x={key.x}
                        y={keyTop}
                        width={key.width}
                        height={keyboardHeight * 0.65}
                        image={key.attacked ? `/blackkey_attack.png` : `/blackkey.png`}
                        filters={key.attacked ? null : [shadowFilter]}
                        eventMode={'static'}
                        mousedown={() => onTriggerAttack(key)}
                        mouseup={() => onTriggerRelease(key)}
                        touchstart={() => onTriggerAttack(key)}
                        touchend={() => onTriggerRelease(key)}
                    >
                    </Sprite>
                })
        }
    </>
}
